{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DweYe9FcbMK_"
      },
      "source": [
        "##### Copyright 2018 The TensorFlow Authors.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "AVV2e0XKbJeX"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "sUtoed20cRJJ"
      },
      "source": [
        "# 텍스트 로드"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "1ap_W4aQcgNT"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/tutorials/load_data/text\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">TensorFlow.org에서 보기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs/blob/master/site/en/tutorials/load_data/text.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Google Colab에서 실행하기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs/blob/master/site/en/tutorials/load_data/text.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">GitHub에서소스 보기</a></td>\n",
        "  <td><a href=\"https://storage.googleapis.com/tensorflow_docs/docs/site/en/tutorials/load_data/text.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\">노트북 다운로드하기</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NWeQAo0Ec_BL"
      },
      "source": [
        "이 튜토리얼은 `tf.data.TextLineDataset`를 사용하여 텍스트 파일에서 예제를 로드하는 방법에 대한 예제를 제공합니다. `TextLineDataset`는 텍스트 파일에서 데이터세트를 작성하도록 설계되었으며, 이때 각 예제는 원본 파일의 텍스트 줄입니다. 주로 줄 기반의 텍스트 데이터(예: 시 또는 오류 로그)에 유용합니다.\n",
        "\n",
        "이 튜토리얼에서는 같은 작품에 대한 3가지 다른 영어 번역본이 있는 Homer's Illiad를 사용하여 한 줄의 텍스트로 번역기를 식별하는 모델을 훈련합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fgZ9gjmPfSnK"
      },
      "source": [
        "## 설정"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "v8sAMTYv5TAr"
      },
      "outputs": [],
      "source": [
        "!pip install -q tfds-nightly"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "baYFZMW_bJHh"
      },
      "outputs": [],
      "source": [
        "import tensorflow as tf\n",
        "\n",
        "import tensorflow_datasets as tfds\n",
        "import os"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YWVWjyIkffau"
      },
      "source": [
        "3가지 번역본은 다음과 같습니다.\n",
        "\n",
        "- [William Cowper](https://en.wikipedia.org/wiki/William_Cowper) — [텍스트](https://storage.googleapis.com/download.tensorflow.org/data/illiad/cowper.txt)\n",
        "\n",
        "- [Edward, Earl of Derby](https://en.wikipedia.org/wiki/Edward_Smith-Stanley,_14th_Earl_of_Derby) — [텍스트](https://storage.googleapis.com/download.tensorflow.org/data/illiad/derby.txt)\n",
        "\n",
        "- [Samuel Butler](https://en.wikipedia.org/wiki/Samuel_Butler_%28novelist%29) — [텍스트](https://storage.googleapis.com/download.tensorflow.org/data/illiad/butler.txt)\n",
        "\n",
        "이 튜토리얼에 사용된 텍스트 파일은 문서 헤더와 바닥 글, 줄 번호, 챕터 제목 등을 제거하는 일반적인 전처리 작업을 거쳤습니다. 이 가볍게 손질한 파일을 로컬로 다운로드합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4YlKQthEYlFw"
      },
      "outputs": [],
      "source": [
        "DIRECTORY_URL = 'https://storage.googleapis.com/download.tensorflow.org/data/illiad/'\n",
        "FILE_NAMES = ['cowper.txt', 'derby.txt', 'butler.txt']\n",
        "\n",
        "for name in FILE_NAMES:\n",
        "  text_dir = tf.keras.utils.get_file(name, origin=DIRECTORY_URL+name)\n",
        "  \n",
        "parent_dir = os.path.dirname(text_dir)\n",
        "\n",
        "parent_dir"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "q3sDy6nuXoNp"
      },
      "source": [
        "## 데이터세트에 텍스트 로드하기\n",
        "\n",
        "파일을 반복하면서 각 파일을 자체 데이터세트에 로드합니다.\n",
        "\n",
        "각 예제에 개별적으로 레이블을 지정해야 하므로 `tf.data.Dataset.map`을 사용하여 각 예제에 labeler 함수를 적용합니다. 이렇게 하면 데이터세트의 모든 예제를 반복하여 (`example, label`) 쌍을 반환합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "K0BjCOpOh7Ch"
      },
      "outputs": [],
      "source": [
        "def labeler(example, index):\n",
        "  return example, tf.cast(index, tf.int64)  \n",
        "\n",
        "labeled_data_sets = []\n",
        "\n",
        "for i, file_name in enumerate(FILE_NAMES):\n",
        "  lines_dataset = tf.data.TextLineDataset(os.path.join(parent_dir, file_name))\n",
        "  labeled_dataset = lines_dataset.map(lambda ex: labeler(ex, i))\n",
        "  labeled_data_sets.append(labeled_dataset)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "M8PHK5J_cXE5"
      },
      "source": [
        "레이블이 지정된 데이터세트를 단일 데이터세트로 결합하고 섞습니다.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "6jAeYkTIi9-2"
      },
      "outputs": [],
      "source": [
        "BUFFER_SIZE = 50000\n",
        "BATCH_SIZE = 64\n",
        "TAKE_SIZE = 5000"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Qd544E-Sh63L"
      },
      "outputs": [],
      "source": [
        "all_labeled_data = labeled_data_sets[0]\n",
        "for labeled_dataset in labeled_data_sets[1:]:\n",
        "  all_labeled_data = all_labeled_data.concatenate(labeled_dataset)\n",
        "  \n",
        "all_labeled_data = all_labeled_data.shuffle(\n",
        "    BUFFER_SIZE, reshuffle_each_iteration=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "r4JEHrJXeG5k"
      },
      "source": [
        "`tf.data.Dataset.take` 및 `print`를 사용하여 `(example, label)` 쌍의 형태를 확인할 수 있습니다. `numpy` 속성은 각 텐서의 값을 보여줍니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gywKlN0xh6u5"
      },
      "outputs": [],
      "source": [
        "for ex in all_labeled_data.take(5):\n",
        "  print(ex)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5rrpU2_sfDh0"
      },
      "source": [
        "## 텍스트 줄을 숫자로 인코딩하기\n",
        "\n",
        "머신러닝 모델은 단어가 아닌 숫자에 대해 동작하므로 문자열 값을 숫자 목록으로 변환해야 합니다. 그렇게 하려면 각 고유한 단어를 고유한 정수에 매핑합니다.\n",
        "\n",
        "### 어휘 만들기\n",
        "\n",
        "먼저, 텍스트를 개별 고유 단어 모음으로 토큰화하여 어휘를 작성합니다. TensorFlow와 Python에서 이를 수행하는 몇 가지 방법이 있습니다. 이 튜토리얼에서 사용한 방법은 다음과 같습니다.\n",
        "\n",
        "1. 각 예제의 `numpy` 값을 반복합니다.\n",
        "2. `tfds.features.text.Tokenizer`를 사용하여 토큰으로 분할합니다.\n",
        "3. 이들 토큰을 Python 세트로 수집하여 중복을 제거합니다.\n",
        "4. 나중에 사용할 수 있도록 어휘의 크기를 구합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "YkHtbGnDh6mg"
      },
      "outputs": [],
      "source": [
        "tokenizer = tfds.features.text.Tokenizer()\n",
        "\n",
        "vocabulary_set = set()\n",
        "for text_tensor, _ in all_labeled_data:\n",
        "  some_tokens = tokenizer.tokenize(text_tensor.numpy())\n",
        "  vocabulary_set.update(some_tokens)\n",
        "\n",
        "vocab_size = len(vocabulary_set)\n",
        "vocab_size"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0W35VJqAh9zs"
      },
      "source": [
        "### 인코딩 예제\n",
        "\n",
        "`vocabulary_set`를 `tfds.features.text.TokenTextEncoder`에 전달하여 encoder를 작성합니다. encoder의 `encode` 메서드는 텍스트 문자열을 받아서 정수 목록을 반환합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gkxJIVAth6j0"
      },
      "outputs": [],
      "source": [
        "encoder = tfds.features.text.TokenTextEncoder(vocabulary_set)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "v6S5Qyabi-vo"
      },
      "source": [
        "한 줄로 시도하여 출력 결과를 확인할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "jgxPZaxUuTbk"
      },
      "outputs": [],
      "source": [
        "example_text = next(iter(all_labeled_data))[0].numpy()\n",
        "print(example_text)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "XoVpKR3qj5yb"
      },
      "outputs": [],
      "source": [
        "encoded_example = encoder.encode(example_text)\n",
        "print(encoded_example)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "p9qHM0v8k_Mg"
      },
      "source": [
        "이제 `tf.py_function`으로 래핑하여 데이터세트의 `map` 메서드에 전달하여 데이터세트에 대해 encoder를 실행합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "HcIQ7LOTh6eT"
      },
      "outputs": [],
      "source": [
        "def encode(text_tensor, label):\n",
        "  encoded_text = encoder.encode(text_tensor.numpy())\n",
        "  return encoded_text, label"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "eES_Z1ia-Om-"
      },
      "source": [
        "`Dataset.map`을 사용하여 이 함수를 데이터세트의 각 요소에 적용하려고 합니다. `Dataset.map`은 그래프 모드에서 실행됩니다.\n",
        "\n",
        "- 그래프 텐서에는 값이 없습니다.\n",
        "- 그래프 모드에서는 TensorFlow Ops 및 함수만 사용할 수 있습니다.\n",
        "\n",
        "따라서 이 함수를 직접 `.map`할 수 없습니다. `tf.py_function`로 래핑해야 합니다. `tf.py_function`은 래핑된 파이썬 함수에 정규 텐서(값과 `.numpy()` 메서드를 사용하여 액세스)를 전달합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "KmQVsAgJ-RM0"
      },
      "outputs": [],
      "source": [
        "def encode_map_fn(text, label):\n",
        "  # py_func doesn't set the shape of the returned tensors.\n",
        "  encoded_text, label = tf.py_function(encode, \n",
        "                                       inp=[text, label], \n",
        "                                       Tout=(tf.int64, tf.int64))\n",
        "\n",
        "  # `tf.data.Datasets` work best if all components have a shape set\n",
        "  #  so set the shapes manually: \n",
        "  encoded_text.set_shape([None])\n",
        "  label.set_shape([])\n",
        "\n",
        "  return encoded_text, label\n",
        "\n",
        "\n",
        "all_encoded_data = all_labeled_data.map(encode_map_fn)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_YZToSXSm0qr"
      },
      "source": [
        "## 데이터세트를 테스트 및 훈련 배치로 분할하기\n",
        "\n",
        "`tf.data.Dataset.take` 및 `tf.data.Dataset.skip`을 사용하여 작은 테스트 데이터세트와 더 큰 훈련 세트를 작성합니다.\n",
        "\n",
        "모델에 전달되기 전에 데이터세트를 배치 처리되어야 합니다. 일반적으로, 배치 내부의 예제는 크기와 형상이 같아야 합니다. 그러나 이 데이터세트의 예제는 모두 같은 크기가 아닙니다. 각 텍스트 줄의 단어 수는 다릅니다. 따라서 `batch` 대신 `tf.data.Dataset.padded_batch`를 사용하여 예제를 같은 크기로 채워야 합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "r-rmbijQh6bf"
      },
      "outputs": [],
      "source": [
        "train_data = all_encoded_data.skip(TAKE_SIZE).shuffle(BUFFER_SIZE)\n",
        "train_data = train_data.padded_batch(BATCH_SIZE)\n",
        "\n",
        "test_data = all_encoded_data.take(TAKE_SIZE)\n",
        "test_data = test_data.padded_batch(BATCH_SIZE)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Xdz7SVwmqi1l"
      },
      "source": [
        "이제 `test_data` 및 `train_data`는 (`example, label`) 쌍의 모음이 아니라 배치의 모음입니다. 각 배치는 배열로 표시되는 한 쌍의 (*많은 예제*, *많은 레이블*)입니다.\n",
        "\n",
        "다음과 같습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "kMslWfuwoqpB"
      },
      "outputs": [],
      "source": [
        "sample_text, sample_labels = next(iter(test_data))\n",
        "\n",
        "sample_text[0], sample_labels[0]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UI4I6_Sa0vWu"
      },
      "source": [
        "새로운 토큰 인코딩(패딩에 사용되는 0)을 도입했기 때문에 어휘 크기가 1씩 증가했습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "IlD1Lli91vuc"
      },
      "outputs": [],
      "source": [
        "vocab_size += 1"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "K8SUhGFNsmRi"
      },
      "source": [
        "## 모델 빌드하기\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "QJgI1pow2YR9"
      },
      "outputs": [],
      "source": [
        "model = tf.keras.Sequential()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wi0iiKLTKdoF"
      },
      "source": [
        "첫 번째 레이어는 정수 표현을 고밀도 벡터 임베딩으로 변환합니다. [단어 임베딩 튜토리얼](../text/word_embeddings.ipynb) 또는 자세한 내용을 참조하세요. "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "DR6-ctbY638P"
      },
      "outputs": [],
      "source": [
        "model.add(tf.keras.layers.Embedding(vocab_size, 64))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_8OJOPohKh1q"
      },
      "source": [
        "다음 레어어는 [Long Short-Term Memory](http://colah.github.io/posts/2015-08-Understanding-LSTMs/) 레이어로, 모델이 문맥에서 단어를 다른 단어와 함께 이해할 수 있도록 합니다. LSTM의 양방향 래퍼는 모델 이전 및 이후에 오는 데이터 포인트와 관련하여 데이터 포인트를 학습하는 데 도움이 됩니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "x6rnq6DN_WUs"
      },
      "outputs": [],
      "source": [
        "model.add(tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(64)))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "cdffbMr5LF1g"
      },
      "source": [
        "최종적으로, 일련의 하나 이상의 조밀하게 연결된 레이어를 갖게 되며 마지막 레이어는 출력 레이어입니다. 출력 레이어는 모든 레이블에 대한 확률을 생성합니다. 확률이 가장 높은 레이블은 예제 레이블의 모델 예측입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "QTEaNSnLCsv5"
      },
      "outputs": [],
      "source": [
        "# One or more dense layers.\n",
        "# Edit the list in the `for` line to experiment with layer sizes.\n",
        "for units in [64, 64]:\n",
        "  model.add(tf.keras.layers.Dense(units, activation='relu'))\n",
        "\n",
        "# Output layer. The first argument is the number of labels.\n",
        "model.add(tf.keras.layers.Dense(3))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "zLHPU8q5DLi_"
      },
      "source": [
        "끝으로, 모델을 컴파일합니다. softmax 분류 모델의 경우, 손실 함수로 `sparse_categorical_crossentropy`를 사용합니다. 다른 옵티마이저를 사용해 볼 수 있지만, `adam`은 매우 일반적입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "pkTBUVO4h6Y5"
      },
      "outputs": [],
      "source": [
        "model.compile(optimizer='adam',\n",
        "              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n",
        "              metrics=['accuracy'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DM-HLo5NDhql"
      },
      "source": [
        "## 모델 훈련하기\n",
        "\n",
        "이 데이터에 대해 실행되는 이 모델은 적절한 결과(약 83%)를 생성합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "aLtO33tNh6V8"
      },
      "outputs": [],
      "source": [
        "model.fit(train_data, epochs=3, validation_data=test_data)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "KTPCYf_Jh6TH"
      },
      "outputs": [],
      "source": [
        "eval_loss, eval_acc = model.evaluate(test_data)\n",
        "\n",
        "print('\\nEval loss: {:.3f}, Eval accuracy: {:.3f}'.format(eval_loss, eval_acc))"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "text.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
